/**
 * combobox - jQuery xui
 *
 * Licensed under the GPL:
 *   http://www.gnu.org/licenses/gpl.txt
 *
 * Copyright 2013 xjb [ beymy.en@gmail.com ]
 *
 */
(function($) {
    /**
     * 构建combobox
     */
    function buildCombobox(target) {
        var t = $(target).addClass('combobox-f').hide();
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var combo = $('<span class="combobox">' +
            '<input type="text" class="combobox-text">' +
            '<span class="combobox-arrow"></span>' +
            '<input type="hidden" class="combobox-value" value="">' +
            '</span>').insertAfter(target);
        var panel = $('<div class="combobox-panel"></div>').appendTo('body');
        var name = t.attr('name');
        $('.combobox-value', combo).attr('name', name);
        t.attr('comboboxName', name).removeAttr('name');

        state.combo = combo;
        state.panel = panel;

        var width = opts.width;
        if (opts.width == 'auto') {
            width = $(target).width();
        }
        combo.width(width);
        $('.combobox-text', combo)._outerWidth(combo.width() - $('.combobox-arrow', combo).outerWidth());
        //panel位置
        /*
        panel.offset({
            left: combo.offset().left,
            top: combo.offset().top + combo.outerHeight()
        });
        */
        panel.width(opts.panelWidth ? opts.panelWidth : width).height(opts.panelHeight)

        setDisabled(target, opts.disabled);
    }

    /**
     * 绑定事件
     */
    function bindEvents(target) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var combo = state.combo;
        var panel = state.panel;
        var input = combo.find('.combobox-text');
        var arrow = combo.find('.combobox-arrow');

        //全局事件
        $(document).unbind('.combobox').bind('mousedown.combobox', function(e) {
            //隐藏没有击中的combobox的下拉面板
            var p = $(e.target).closest('div.combobox-panel');
            var panels = $('body>div.combobox-panel:visible');
            if (p.length) {
                panels = panels.not(p[0]);
            }
            p = $(e.target).closest('span.combobox-arrow');
            if (p.length) {
                p = p.parent().prev().combobox('panel');
                panels = panels.not(p[0]);
            }
            panels.css('display', 'none');
        });


        //先取消绑定事件
        input.unbind('.combobox');
        arrow.unbind('.combobox');
        panel.unbind('.combobox');
        if (!opts.disabled) {
            //输入框
            input.bind('keydown.combobox', function(e) {
                switch (e.keyCode) {
                    case 37: //left
                    case 38: //up
                        nav(target, 'prev');
                        break;
                    case 39: //right
                    case 40: //down
                        nav(target, 'next');
                        break;
                    case 13: //enter
                        onEnter(target);
                        break;
                    case 9: //tab
                    case 27: //esc
                        hidePanel(target);
                        break;
                    default:
                        if (state.timer) {
                            clearTimeout(state.timer);
                        }
                        state.timer = setTimeout(function() {
                            var q = input.val();
                            if (state.previousValue != q) {
                                state.previousValue = q;
                                showPanel(target);
                                onQuery(target, q);
                            }
                        }, opts.delay);
                        break;
                }
            });

            //下拉箭头
            arrow.bind('mouseover.combobox', function() {
                $(this).addClass('combobox-arrow-hover');
            }).bind('mouseout.combobox', function() {
                $(this).removeClass('combobox-arrow-hover');
            }).bind('click.combobox', function() {
                //聚焦且光标放到文本最后
                input.focus().val(input.val());
                togglePanel(target);
            });
            //下拉选项
            $('.combobox-item', panel).bind('mouseover.combobox', function() {
                $(this).addClass('combobox-item-hover');
            }).bind('mouseout.combobox', function() {
                $(this).removeClass('combobox-item-hover');
            }).bind('click.combobox', function(e) {
                var item = $(this);
                var value = item.attr('value');
                if (opts.multiple) {
                    if (item.hasClass('combobox-item-selected')) {
                        unselect(target, value);
                    } else {
                        select(target, value);
                    }
                } else {
                    select(target, value);
                    hidePanel(target);
                }
            });
        }
    }

    /**
     * 显示下拉面板
     */
    function showPanel(target) {
    	var state = $.data(target, 'combobox');
        var panel = state.panel;
        var combo = state.combo;
        if ($.fn.dialog) {
            panel.css('z-index', $.fn.dialog.defaults.zIndex++);
        }
        panel.css({
            left: combo.offset().left,
            top: combo.offset().top + combo.outerHeight(),
            display: 'block'
        });
    }

    /**
     * 隐藏下拉面板
     */
    function hidePanel(target) {
        var panel = $.data(target, 'combobox').panel;
        panel.css('display', 'none');
    }

    /**
     * 切换下拉面板
     */
    function togglePanel(target) {
        var panel = $.data(target, 'combobox').panel;

        if (panel.is(':visible')) {
            hidePanel(target);
        } else {
            showPanel(target);
        }
    }

    function findDataItem(data, key, value) {
        for (var i = 0; i < data.length; i++) {
            var item = data[i];
            if (item[key] == value) {
                return item
            }
        }
        return null;
    }

    /**
     * 滚动滚动条显示指定项
     */
    function scrollTo(target, value) {
        var state = $.data(target, 'combobox');
        var panel = state.panel;

        var item = panel.find('div.combobox-item[value="' + value + '"]');
        if (item.length) {
            if (item.position().top <= 0) {
                var h = panel.scrollTop() + item.position().top;
                panel.scrollTop(h);
            } else if (item.position().top + item.outerHeight() > panel.height()) {
                var h = panel.scrollTop() + item.position().top + item.outerHeight() - panel.height();
                panel.scrollTop(h);
            }
        }
    }

    function nav(target, dir) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var panel = state.panel;

        var item = panel.children('div.combobox-item-hover');
        if (!item.length) {
            item = panel.children('div.combobox-item-selected');
        }
        item.removeClass('combobox-item-hover');
        if (!item.length) {
            item = panel.children('div.combobox-item:visible:' + (dir == 'next' ? 'first' : 'last'));
        } else {
            if (dir == 'next') {
                item = item.nextAll('div.combobox-item:visible:first');
                if (!item.length) {
                    item = panel.children('div.combobox-item:visible:first');
                }
            } else {
                item = item.prevAll('div.combobox-item:visible:first');
                if (!item.length) {
                    item = panel.children('div.combobox-item:visible:last');
                }
            }
        }
        if (item.length) {
            item.addClass('combobox-item-hover');
            scrollTo(target, item.attr('value'));
            //select(target, item.attr('value'));
        }
    }

    /**
     * 选中指定选项
     */
    function select(target, value) {
        var opts = $.data(target, 'combobox').options;
        var data = $.data(target, 'combobox').data;

        if (opts.multiple) {
            var values = getValues(target);
            for (var i = 0; i < values.length; i++) {
                if (values[i] == value) return;
            }
            values.push(value);
            setValues(target, values);
        } else {
            setValues(target, [value]);
        }

        var item = findDataItem(data, opts.valueField, value);
        if (item) {
            opts.onSelect.call(target, item);
        }
    }

    /**
     * 取消选中指定选项
     */
    function unselect(target, value) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var values = getValues(target);
        var index = $.inArray(value + '', values);
        if (index >= 0) {
            values.splice(index, 1);
            setValues(target, values);
        }
        var item = findDataItem(state.data, opts.valueField, value);
        if (item) {
            opts.onUnselect.call(target, item);
        }
    }

    function setDisabled(target, disabled) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var combo = state.combo;

        if (disabled) {
            opts.disabled = true;
            $(target).attr('disabled', 'disabled');
            combo.find('.combobox-value').attr('disabled', 'disabled');
            combo.find('.combobox-text').attr('disabled', 'disabled');
        } else {
            opts.disabled = false;
            $(target).removeAttr('disabled');
            combo.find('.combobox-value').removeAttr('disabled');
            combo.find('.combobox-text').removeAttr('disabled');
        }
    }

    function getText(target) {
        var combo = $.data(target, 'combobox').combo;
        return combo.find('input.combobox-text').val();
    }

    function setText(target, text) {
        var combo = $.data(target, 'combobox').combo;
        combo.find('input.combobox-text').val(text);
    }

    /**
     * 获取值
     */
    function getValues(target) {
        var state = $.data(target, 'combobox');
        var panel = state.panel;
        var values = [];
        panel.find('div.combobox-item-selected').each(function() {
            values.push($(this).attr('value'));
        });
        return values;
    }

    /**
     * 设置值
     */
    function setValues(target, values) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var data = state.data;
        var combo = state.combo;
        var panel = state.panel;

        panel.find('div.combobox-item-selected').removeClass('combobox-item-selected');

        var vv = [],
            ss = [];
        for (var i = 0; i < values.length; i++) {
            var v = values[i];
            var s = v;
            var item = findDataItem(data, opts.valueField, v);
            if (item) {
                s = item[opts.textField];
            }
            vv.push(v);
            ss.push(s);
            panel.find('div.combobox-item[value="' + v + '"]').addClass('combobox-item-selected');
        }
        //值
				combo.find('.combobox-value').val(vv.join(opts.separator));
				//文本
        setText(target, ss.join(opts.separator));
    }

    /**
     * 加载数据
     */
    function loadData(target, data) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var combo = state.combo;
        var panel = state.panel;
        state.data = opts.loadFilter.call(target, data);
        data = state.data;

        var dd = [];
        for (var i = 0; i < data.length; i++) {
            var item = data[i];
            var v = item[opts.valueField];
            var s = item[opts.textField];

            dd.push('<div class="combobox-item" value="' + v + '">');
            dd.push(opts.formatter ? opts.formatter.call(target, item) : s);
            dd.push('</div>');
        }
        panel.html(dd.join(''));

        opts.onLoadSuccess.call(target, data);
    }

    /**
     * 请求远程数据
     */
    function request(target, url, param) {
        var opts = $.data(target, 'combobox').options;
        if (url) {
            opts.url = url;
        }
        if (!opts.url)
            return;
        param = param || {};

        if (opts.onBeforeLoad.call(target, param) == false)
            return;

        $.ajax({
            type: opts.method,
            url: opts.url,
            data: param,
            dataType: 'json',
            success: function(data) {
                loadData(target, data);
            },
            error: function() {
                opts.onLoadError.apply(this, arguments);
            }
        });
    }

    /**
     * 查询
     */
    function onQuery(target, q) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var data = state.data;
        var combo = state.combo;
        var panel = state.panel;

        if (opts.mode == 'remote') {
            request(target, null, {
                q: q
            }, true);
        } else {
            panel.find('div.combobox-item').hide();
            for (var i = 0; i < data.length; i++) {
                var item = data[i];
                if (opts.filter.call(target, q, item)) {
                    var v = item[opts.valueField];
                    panel.find('div.combobox-item[value="' + v + '"]').show();
                }
            }
        }
    }

    /**
     * 回车
     */
    function onEnter(target) {
        var state = $.data(target, 'combobox');
        var opts = state.options;
        var data = state.data;
        var combo = state.combo;
        var panel = state.panel;

        //获取hover或selected项
        var item = panel.children('div.combobox-item-hover');
        if (!item.length) {
            item = panel.children('div.combobox-item-selected');
        }
        if (!item.length) {
            return;
        }
        if (opts.multiple) {
            if (item.hasClass('combobox-item-selected')) {
                unselect(target, item.attr('value'));
            } else {
                select(target, item.attr('value'));
            }
        } else {
            select(target, item.attr('value'));
            hidePanel(target);
        }
    }

    $.fn.combobox = function(options, param) {
        if (typeof options == 'string') {
            return $.fn.combobox.methods[options](this, param);
        }

        options = options || {};
        return this.each(function() {
            var state = $.data(this, 'combobox');
            var opts;
            if (state) {
                opts = $.extend(state.options, options);
            } else {
            		opts = $.extend({}, $.fn.combobox.defaults, $.fn.combobox.parseOptions(this), options);
                state = $.data(this, 'combobox', {
                    options: opts,
                    data: [],
                    values: []
                });
                buildCombobox(this);
                var data = $.fn.combobox.parseData(this);
                if (data.length) {
                    loadData(this, data);
                }
            }
            if (options.data) {
                loadData(this, opts.data);
            }
            request(this);
            bindEvents(this);
        });
    };


    $.fn.combobox.methods = {
        options: function(jq) {
            return jq.length > 0 ? $.data(jq[0], 'combobox').options : null;
        },
        getData: function(jq) {
            return jq.length > 0 ? $.data(jq[0], 'combobox').data : null;
        },
        panel: function(jq) {
            return jq.length > 0 ? $.data(jq[0], 'combobox').panel : null;
        },
        setValues: function(jq, values) {
            return jq.each(function() {
                setValues(this, values);
            });
        },
        setValue: function(jq, value) {
            return jq.each(function() {
                setValues(this, [value]);
            });
        },
        disable: function(jq) {
            return jq.each(function() {
                setDisabled(this, true);
                bindEvents(this);
            });
        },
        enable: function(jq) {
            return jq.each(function() {
                setDisabled(this, false);
                bindEvents(this);
            });
        },
        clear: function(jq) {
            return jq.each(function() {
                var panel = $.data(target, 'combobox').panel;
                panel.find('div.combobox-item-selected').removeClass('combobox-item-selected');
            });
        },
        loadData: function(jq, data) {
            return jq.each(function() {
                loadData(this, data);
            });
        },
        reload: function(jq, url) {
            return jq.each(function() {
                request(this, url);
            });
        },
        select: function(jq, value) {
            return jq.each(function() {
                select(this, value);
            });
        },
        unselect: function(jq, value) {
            return jq.each(function() {
                unselect(this, value);
            });
        }
    };

    $.fn.combobox.parseOptions = function(target) {
        var t = $(target);
        return $.extend({}, $.parser.parseOptions(target, [
            'valueField', 'textField', 'mode', 'method', 'url'
        ]));
    };

    $.fn.combobox.parseData = function(target) {
        var data = [];
        var opts = $(target).combobox('options');
        $(target).children().each(function() {
            var t = $(this);
            var item = {};
            item[opts.valueField] = t.attr('value') || t.html();
            item[opts.textField] = t.html();
            data.push(item);
        });
        return data;
    };

    $.fn.combobox.defaults = {
        width: 'auto',
        panelWidth: null,
        panelHeight: 200,
        multiple: false,
        separator: ',',
        editable: true,
        disabled: false,
        valueField: 'value',
        textField: 'text',
        mode: 'local', // or 'remote'
        method: 'post',
        url: null,
        data: null,
        delay: 200,
        filter: function(q, row) {
            var opts = $(this).combobox('options');
            return row[opts.textField].indexOf(q) >= 0;
        },
        formatter: function(row) {
            var opts = $(this).combobox('options');
            return row[opts.textField];
        },
        loadFilter: function(data) {
            return data;
        },
        onBeforeLoad: function(param) {},
        onLoadSuccess: function() {},
        onLoadError: function() {},
        onSelect: function(record) {},
        onUnselect: function(record) {}
    };
})(jQuery);
